home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / guile / 1.8 / ice-9 / ftw.scm < prev    next >
Encoding:
Text File  |  2008-12-17  |  16.7 KB  |  381 lines

  1. ;;;; ftw.scm --- filesystem tree walk
  2.  
  3. ;;;;     Copyright (C) 2002, 2003, 2006 Free Software Foundation, Inc.
  4. ;;;;
  5. ;;;; This library is free software; you can redistribute it and/or
  6. ;;;; modify it under the terms of the GNU Lesser General Public
  7. ;;;; License as published by the Free Software Foundation; either
  8. ;;;; version 2.1 of the License, or (at your option) any later version.
  9. ;;;; 
  10. ;;;; This library is distributed in the hope that it will be useful,
  11. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13. ;;;; Lesser General Public License for more details.
  14. ;;;; 
  15. ;;;; You should have received a copy of the GNU Lesser General Public
  16. ;;;; License along with this library; if not, write to the Free Software
  17. ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18.  
  19. ;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
  20.  
  21. ;;; Commentary:
  22.  
  23. ;; Two procedures are provided: `ftw' and `nftw'.
  24.  
  25. ;; NOTE: The following description was adapted from the GNU libc info page, w/
  26. ;; significant modifications for a more "Schemey" interface.  Most noticible
  27. ;; are the inlining of `struct FTW *' parameters `base' and `level' and the
  28. ;; omission of `descriptors' parameters.
  29.  
  30. ;; * Types
  31. ;;
  32. ;;    The X/Open specification defines two procedures to process whole
  33. ;; hierarchies of directories and the contained files.  Both procedures
  34. ;; of this `ftw' family take as one of the arguments a callback procedure
  35. ;; which must be of these types.
  36. ;;
  37. ;;  - Data Type: __ftw_proc_t
  38. ;;           (lambda (filename statinfo flag) ...) => status
  39. ;;
  40. ;;      Type for callback procedures given to the `ftw' procedure.  The
  41. ;;      first parameter is a filename, the second parameter is the
  42. ;;      vector value as returned by calling `stat' on FILENAME.
  43. ;;
  44. ;;      The last parameter is a symbol giving more information about
  45. ;;      FILENAM.  It can have one of the following values:
  46. ;;
  47. ;;     `regular'
  48. ;;           The current item is a normal file or files which do not fit
  49. ;;           into one of the following categories.  This means
  50. ;;           especially special files, sockets etc.
  51. ;;
  52. ;;     `directory'
  53. ;;           The current item is a directory.
  54. ;;
  55. ;;     `invalid-stat'
  56. ;;           The `stat' call to fill the object pointed to by the second
  57. ;;           parameter failed and so the information is invalid.
  58. ;;
  59. ;;     `directory-not-readable'
  60. ;;           The item is a directory which cannot be read.
  61. ;;
  62. ;;     `symlink'
  63. ;;           The item is a symbolic link.  Since symbolic links are
  64. ;;           normally followed seeing this value in a `ftw' callback
  65. ;;           procedure means the referenced file does not exist.  The
  66. ;;           situation for `nftw' is different.
  67. ;;
  68. ;;  - Data Type: __nftw_proc_t
  69. ;;           (lambda (filename statinfo flag base level) ...) => status
  70. ;;
  71. ;;      The first three arguments have the same as for the
  72. ;;      `__ftw_proc_t' type.  A difference is that for the third
  73. ;;      argument some additional values are defined to allow finer
  74. ;;      differentiation:
  75. ;;
  76. ;;     `directory-processed'
  77. ;;           The current item is a directory and all subdirectories have
  78. ;;           already been visited and reported.  This flag is returned
  79. ;;           instead of `directory' if the `depth' flag is given to
  80. ;;           `nftw' (see below).
  81. ;;
  82. ;;     `stale-symlink'
  83. ;;           The current item is a stale symbolic link.  The file it
  84. ;;           points to does not exist.
  85. ;;
  86. ;;      The last two parameters are described below.  They contain
  87. ;;      information to help interpret FILENAME and give some information
  88. ;;      about current state of the traversal of the directory hierarchy.
  89. ;;
  90. ;;     `base'
  91. ;;           The value specifies which part of the filename argument
  92. ;;           given in the first parameter to the callback procedure is
  93. ;;           the name of the file.  The rest of the string is the path
  94. ;;           to locate the file.  This information is especially
  95. ;;           important if the `chdir' flag for `nftw' was set since then
  96. ;;           the current directory is the one the current item is found
  97. ;;           in.
  98. ;;
  99. ;;     `level'
  100. ;;           While processing the directory the procedures tracks how
  101. ;;           many directories have been examined to find the current
  102. ;;           item.  This nesting level is 0 for the item given starting
  103. ;;           item (file or directory) and is incremented by one for each
  104. ;;           entered directory.
  105. ;;
  106. ;; * Procedure: (ftw filename proc . options)
  107. ;;   Do a filesystem tree walk starting at FILENAME using PROC.
  108. ;;
  109. ;;   The `ftw' procedure calls the callback procedure given in the
  110. ;;   parameter PROC for every item which is found in the directory
  111. ;;   specified by FILENAME and all directories below.  The procedure
  112. ;;   follows symbolic links if necessary but does not process an item
  113. ;;   twice.  If FILENAME names no directory this item is the only
  114. ;;   object reported by calling the callback procedure.
  115. ;;
  116. ;;   The filename given to the callback procedure is constructed by
  117. ;;   taking the FILENAME parameter and appending the names of all
  118. ;;   passed directories and then the local file name.  So the
  119. ;;   callback procedure can use this parameter to access the file.
  120. ;;   Before the callback procedure is called `ftw' calls `stat' for
  121. ;;   this file and passes the information up to the callback
  122. ;;   procedure.  If this `stat' call was not successful the failure is
  123. ;;   indicated by setting the flag argument of the callback procedure
  124. ;;   to `invalid-stat'.  Otherwise the flag is set according to the
  125. ;;   description given in the description of `__ftw_proc_t' above.
  126. ;;
  127. ;;   The callback procedure is expected to return non-#f to indicate
  128. ;;   that no error occurred and the processing should be continued.
  129. ;;   If an error occurred in the callback procedure or the call to
  130. ;;   `ftw' shall return immediately the callback procedure can return
  131. ;;   #f.  This is the only correct way to stop the procedure.  The
  132. ;;   program must not use `throw' or similar techniques to continue
  133. ;;   the program in another place.  [Can we relax this? --ttn]
  134. ;;
  135. ;;   The return value of the `ftw' procedure is #t if all callback
  136. ;;   procedure calls returned #t and all actions performed by the
  137. ;;   `ftw' succeeded.  If some procedure call failed (other than
  138. ;;   calling `stat' on an item) the procedure returns #f.  If a
  139. ;;   callback procedure returns a value other than #t this value is
  140. ;;   returned as the return value of `ftw'.
  141. ;;
  142. ;; * Procedure: (nftw filename proc . control-flags)
  143. ;;   Do a new-style filesystem tree walk starting at FILENAME using PROC.
  144. ;;   Various optional CONTROL-FLAGS alter the default behavior.
  145. ;;
  146. ;;   The `nftw' procedures works like the `ftw' procedures.  It calls
  147. ;;   the callback procedure PROC for all items it finds in the
  148. ;;   directory FILENAME and below.
  149. ;;
  150. ;;   The differences are that for one the callback procedure is of a
  151. ;;   different type.  It takes also `base' and `level' parameters as
  152. ;;   described above.
  153. ;;
  154. ;;   The second difference is that `nftw' takes additional optional
  155. ;;   arguments which are zero or more of the following symbols:
  156. ;;
  157. ;;   physical'
  158. ;;        While traversing the directory symbolic links are not
  159. ;;        followed.  I.e., if this flag is given symbolic links are
  160. ;;        reported using the `symlink' value for the type parameter
  161. ;;        to the callback procedure.  Please note that if this flag is
  162. ;;        used the appearance of `symlink' in a callback procedure
  163. ;;        does not mean the referenced file does not exist.  To
  164. ;;        indicate this the extra value `stale-symlink' exists.
  165. ;;
  166. ;;   mount'
  167. ;;        The callback procedure is only called for items which are on
  168. ;;        the same mounted filesystem as the directory given as the
  169. ;;        FILENAME parameter to `nftw'.
  170. ;;
  171. ;;   chdir'
  172. ;;        If this flag is given the current working directory is
  173. ;;        changed to the directory containing the reported object
  174. ;;        before the callback procedure is called.
  175. ;;
  176. ;;   depth'
  177. ;;        If this option is given the procedure visits first all files
  178. ;;        and subdirectories before the callback procedure is called
  179. ;;        for the directory itself (depth-first processing).  This
  180. ;;        also means the type flag given to the callback procedure is
  181. ;;        `directory-processed' and not `directory'.
  182. ;;
  183. ;;   The return value is computed in the same way as for `ftw'.
  184. ;;   `nftw' returns #t if no failure occurred in `nftw' and all
  185. ;;   callback procedure call return values are also #t.  For internal
  186. ;;   errors such as memory problems the error `ftw-error' is thrown.
  187. ;;   If the return value of a callback invocation is not #t this
  188. ;;   very same value is returned.
  189.  
  190. ;;; Code:
  191.  
  192. (define-module (ice-9 ftw)
  193.   :export (ftw nftw))
  194.  
  195. (define (directory-files dir)
  196.   (let ((dir-stream (opendir dir)))
  197.     (let loop ((new (readdir dir-stream))
  198.                (acc '()))
  199.       (if (eof-object? new)
  200.       (begin
  201.         (closedir dir-stream)
  202.         acc)
  203.           (loop (readdir dir-stream)
  204.                 (if (or (string=? "."  new)             ;;; ignore
  205.                         (string=? ".." new))            ;;; ignore
  206.                     acc
  207.                     (cons new acc)))))))
  208.  
  209. (define (pathify . nodes)
  210.   (let loop ((nodes nodes)
  211.              (result ""))
  212.     (if (null? nodes)
  213.         (or (and (string=? "" result) "")
  214.             (substring result 1 (string-length result)))
  215.         (loop (cdr nodes) (string-append result "/" (car nodes))))))
  216.  
  217. (define (abs? filename)
  218.   (char=? #\/ (string-ref filename 0)))
  219.  
  220. ;; `visited?-proc' returns a test procedure VISITED? which when called as
  221. ;; (VISITED? stat-obj) returns #f the first time a distinct file is seen,
  222. ;; then #t on any subsequent sighting of it.
  223. ;;
  224. ;; stat:dev and stat:ino together uniquely identify a file (see "Attribute
  225. ;; Meanings" in the glibc manual).  Often there'll be just one dev, and
  226. ;; usually there's just a handful mounted, so the strategy here is a small
  227. ;; hash table indexed by dev, containing hash tables indexed by ino.
  228. ;;
  229. ;; It'd be possible to make a pair (dev . ino) and use that as the key to a
  230. ;; single hash table.  It'd use an extra pair for every file visited, but
  231. ;; might be a little faster if it meant less scheme code.
  232. ;;
  233. (define (visited?-proc size)
  234.   (let ((dev-hash (make-hash-table 7)))
  235.     (lambda (s)
  236.       (and s
  237.        (let ((ino-hash (hashv-ref dev-hash (stat:dev s)))
  238.          (ino      (stat:ino s)))
  239.          (or ino-hash
  240.          (begin
  241.            (set! ino-hash (make-hash-table size))
  242.            (hashv-set! dev-hash (stat:dev s) ino-hash)))
  243.          (or (hashv-ref ino-hash ino)
  244.          (begin
  245.            (hashv-set! ino-hash ino #t)
  246.            #f)))))))
  247.  
  248. (define (stat-dir-readable?-proc uid gid)
  249.   (let ((uid (getuid))
  250.         (gid (getgid)))
  251.     (lambda (s)
  252.       (let* ((perms (stat:perms s))
  253.              (perms-bit-set? (lambda (mask)
  254.                                (not (= 0 (logand mask perms))))))
  255.         (or (and (= uid (stat:uid s))
  256.                  (perms-bit-set? #o400))
  257.             (and (= gid (stat:gid s))
  258.                  (perms-bit-set? #o040))
  259.             (perms-bit-set? #o004))))))
  260.  
  261. (define (stat&flag-proc dir-readable? . control-flags)
  262.   (let* ((directory-flag (if (memq 'depth control-flags)
  263.                              'directory-processed
  264.                              'directory))
  265.          (stale-symlink-flag (if (memq 'nftw-style control-flags)
  266.                                  'stale-symlink
  267.                                  'symlink))
  268.          (physical? (memq 'physical control-flags))
  269.          (easy-flag (lambda (s)
  270.                       (let ((type (stat:type s)))
  271.                         (if (eq? 'directory type)
  272.                             (if (dir-readable? s)
  273.                                 directory-flag
  274.                                 'directory-not-readable)
  275.                             'regular)))))
  276.     (lambda (name)
  277.       (let ((s (false-if-exception (lstat name))))
  278.         (cond ((not s)
  279.                (values s 'invalid-stat))
  280.               ((eq? 'symlink (stat:type s))
  281.                (let ((s-follow (false-if-exception (stat name))))
  282.                  (cond ((not s-follow)
  283.                         (values s stale-symlink-flag))
  284.                        ((and s-follow physical?)
  285.                         (values s 'symlink))
  286.                        ((and s-follow (not physical?))
  287.                         (values s-follow (easy-flag s-follow))))))
  288.               (else (values s (easy-flag s))))))))
  289.  
  290. (define (clean name)
  291.   (let ((last-char-index (1- (string-length name))))
  292.     (if (char=? #\/ (string-ref name last-char-index))
  293.         (substring name 0 last-char-index)
  294.         name)))
  295.  
  296. (define (ftw filename proc . options)
  297.   (let* ((visited? (visited?-proc (cond ((memq 'hash-size options) => cadr)
  298.                                         (else 211))))
  299.          (stat&flag (stat&flag-proc
  300.                      (stat-dir-readable?-proc (getuid) (getgid)))))
  301.     (letrec ((go (lambda (fullname)
  302.                    (call-with-values (lambda () (stat&flag fullname))
  303.                      (lambda (s flag)
  304.                        (or (visited? s)
  305.                            (let ((ret (proc fullname s flag))) ; callback
  306.                              (or (eq? #t ret)
  307.                                  (throw 'ftw-early-exit ret))
  308.                              (and (eq? 'directory flag)
  309.                                   (for-each
  310.                                    (lambda (child)
  311.                                      (go (pathify fullname child)))
  312.                                    (directory-files fullname)))
  313.                              #t)))))))
  314.       (catch 'ftw-early-exit
  315.              (lambda () (go (clean filename)))
  316.              (lambda (key val) val)))))
  317.  
  318. (define (nftw filename proc . control-flags)
  319.   (let* ((od (getcwd))                  ; orig dir
  320.          (odev (let ((s (false-if-exception (lstat filename))))
  321.                  (if s (stat:dev s) -1)))
  322.          (same-dev? (if (memq 'mount control-flags)
  323.                         (lambda (s) (= (stat:dev s) odev))
  324.                         (lambda (s) #t)))
  325.          (base-sub (lambda (name base) (substring name 0 base)))
  326.          (maybe-cd (if (memq 'chdir control-flags)
  327.                        (if (abs? filename)
  328.                            (lambda (fullname base)
  329.                              (or (= 0 base)
  330.                                  (chdir (base-sub fullname base))))
  331.                            (lambda (fullname base)
  332.                              (chdir
  333.                               (pathify od (base-sub fullname base)))))
  334.                        (lambda (fullname base) #t)))
  335.          (maybe-cd-back (if (memq 'chdir control-flags)
  336.                             (lambda () (chdir od))
  337.                             (lambda () #t)))
  338.          (depth-first? (memq 'depth control-flags))
  339.          (visited? (visited?-proc
  340.                     (cond ((memq 'hash-size control-flags) => cadr)
  341.                           (else 211))))
  342.          (has-kids? (if depth-first?
  343.                         (lambda (flag) (eq? flag 'directory-processed))
  344.                         (lambda (flag) (eq? flag 'directory))))
  345.          (stat&flag (apply stat&flag-proc
  346.                            (stat-dir-readable?-proc (getuid) (getgid))
  347.                            (cons 'nftw-style control-flags))))
  348.     (letrec ((go (lambda (fullname base level)
  349.                    (call-with-values (lambda () (stat&flag fullname))
  350.                      (lambda (s flag)
  351.                        (letrec ((self (lambda ()
  352.                                         (maybe-cd fullname base)
  353.                                         ;; the callback
  354.                                         (let ((ret (proc fullname s flag
  355.                                                          base level)))
  356.                                           (maybe-cd-back)
  357.                                           (or (eq? #t ret)
  358.                                               (throw 'nftw-early-exit ret)))))
  359.                                 (kids (lambda ()
  360.                                         (and (has-kids? flag)
  361.                                              (for-each
  362.                                               (lambda (child)
  363.                                                 (go (pathify fullname child)
  364.                                                     (1+ (string-length
  365.                                                          fullname))
  366.                                                     (1+ level)))
  367.                                               (directory-files fullname))))))
  368.                          (or (visited? s)
  369.                              (not (same-dev? s))
  370.                              (if depth-first?
  371.                                  (begin (kids) (self))
  372.                                  (begin (self) (kids)))))))
  373.                    #t)))
  374.       (let ((ret (catch 'nftw-early-exit
  375.                         (lambda () (go (clean filename) 0 0))
  376.                         (lambda (key val) val))))
  377.         (chdir od)
  378.         ret))))
  379.  
  380. ;;; ftw.scm ends here
  381.